package cc.lj1.auth.services;

import cc.lj1.auth.AuthenticatableUser;
import cc.lj1.auth.core.utils.CacheUtils;
import cc.lj1.auth.helper.ApplicationContextUtils;
import cc.lj1.auth.properties.AuthProperties;
import com.alibaba.fastjson.JSON;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Base64;
import java.util.UUID;

@Component
public class AuthTokenService {
    private static final String PREFIX_TOKENMAP = "T2U.";
    private static final String PREFIX_USERMAP = "U2T.";

    private AuthProperties authProperties;
    private CacheUtils cacheUtils;

    public AuthTokenService(
            AuthProperties authProperties,
            CacheUtils  cacheUtils
    ) {
        this.authProperties = authProperties;
        this.cacheUtils = cacheUtils;
        Token.setSecret(authProperties.getSecret());
    }

    public AuthenticatableUser check(Token token, String agentType) {
        AuthenticatableUser user = null;
        if(token != null) {
            final String groupId = token.getGroupId();
            final String uuid = token.getUuid();
            AuthProperties.AgentInfo agentInfo = authProperties.getAgentInfo(agentType);
            String k = PREFIX_TOKENMAP + agentInfo.getPrefix() + groupId + "." + uuid;
            String uid = cacheUtils.getString(k);
            if (StringUtils.hasLength(uid)) {
                try {
                    AuthUserService authUserService = ApplicationContextUtils.getBean(groupId, AuthUserService.class);
//                    AuthUserService authUserService = StringUtils.hasLength(groupId) ?
//                            ApplicationContextUtils.getBean(groupId, AuthUserService.class) :
//                            ApplicationContextUtils.getBean(AuthUserService.class);
                    user = authUserService != null ? authUserService.getUserByPrimaryKey(uid) : null;
                }
                catch (Exception e) {
                    e.printStackTrace();
                    user = null;
                }
                if (user == null) {
                    cacheUtils.setString(k, null);
                } else {
                    cacheUtils.setExpire(k, agentInfo.getExpire());
                }
            }
        }
        return user;
    }

    public String create(String group, String uid, String agentType) {
        if(uid == null || uid.isEmpty())
            return null;
        AuthProperties.AgentInfo agentInfo = authProperties.getAgentInfo(agentType);
        if(agentInfo.getConflict()) {
            conflict(uid, agentInfo.getName());
        }
        Token token = new Token(uid, group);
        String t2uKey = agentInfo.getPrefix() + token.getGroupId() + "." + token.getUuid();
        String k = PREFIX_TOKENMAP + t2uKey;
        String k2 = PREFIX_USERMAP + token.getGroupId() + "." + uid;
        cacheUtils.setString(k, uid, agentInfo.getExpire());
        cacheUtils.pushToList(k2, t2uKey);

        return token.toString();
    }

    public String create(String uid, String agentType) {
        return create("", uid, agentType);
    }

    public void remove(Token token, String agentType) {
        if(token != null) {
            AuthProperties.AgentInfo agentInfo = authProperties.getAgentInfo(agentType);
            String t2uKey = agentInfo.getPrefix() + token.getGroupId() + "." + token.getUuid();
            String k = PREFIX_TOKENMAP + t2uKey;
            cacheUtils.setString(k, null);
            String uid = cacheUtils.getString(k);
            if (uid != null && !uid.isEmpty()) {
                String k2 = PREFIX_USERMAP + token.getGroupId() + "." + uid;
                cacheUtils.takeFromList(k2, t2uKey);
            }
        }
    }

    public void remove(String token, String agentType) {
        remove(Token.parse(token), agentType);
    }

    public void conflict(String group, String uid, String agentType) {
        String[] list = cacheUtils.getList(PREFIX_USERMAP + group + "." + uid);
        AuthProperties.AgentInfo agentInfo = (agentType == null) ? null : authProperties.getAgentInfo(agentType);
        String prefix = agentInfo == null ? null : agentInfo.getPrefix();
        if(list != null && list.length > 0) {
            for (String item: list) {
                if(prefix == null || item.startsWith(prefix))
                    cacheUtils.setString(PREFIX_TOKENMAP + item, null);
            }
        }
    }

    public void conflict(String uid, String agentType) {
        conflict("", uid, agentType);
    }

    public static class Token {
        private static String secret = "";

        private String userId;
        private String groupId;
        private String uuid;

        public Token() {
            this.userId = "";
            this.groupId = "";
            this.uuid = cleanUUID();
        }

        public Token(String userId) {
            this.userId = userId;
            this.groupId = "";
            this.uuid = cleanUUID();
        }

        public Token(String userId, String groupId) {
            this.userId = userId;
            this.groupId = groupId;
            this.uuid = cleanUUID();
        }

        public Token(String userId, String groupId, String uuid) {
            this.userId = userId;
            this.groupId = groupId;
            this.uuid = uuid;
        }

        public String getUserId() {
            return userId;
        }

        public String getGroupId() {
            return groupId;
        }

        public String getUuid() {
            return uuid;
        }

        public Token setUserId(String userId) {
            this.userId = userId;
            return this;
        }

        public Token setGroupId(String groupId) {
            this.groupId = groupId;
            return this;
        }

        public Token setUuid(String uuid) {
            this.uuid = uuid;
            return this;
        }

        public String toString() {
            String jsonString = JSON.toJSONString(this);
            try {
                MessageDigest digest = MessageDigest.getInstance("SHA-256");
                digest.update(jsonString.getBytes());
                digest.update(this.userId.getBytes());
                digest.update(Token.secret.getBytes());
                byte[] sign = digest.digest();
                final String encodedSign = Base64.getEncoder().encodeToString(sign).substring(0, 43);
                final String encodedPayload = Base64.getEncoder().encodeToString(jsonString.getBytes());
                int pos = encodedPayload.indexOf('=');
                return (encodedSign + (pos >= 0 ? encodedPayload.substring(0, pos) : encodedPayload))
                        .replace('+', '-').replace('/', '_');
            }
            catch (Exception e) {
                return null;
            }
        }

        public static String getSecret() {
            return secret;
        }

        public static void setSecret(String secret) {
            Token.secret = secret;
        }

        public static Token parse(String token) {
            try {
                token = token.replace('-', '+').replace('_', '/');
                String sign = token.substring(0, 43);// + "=";
                String payload = token.substring(43);
                byte[] rawSign = Base64.getDecoder().decode(sign);
                String jsonPayload = new String(Base64.getDecoder().decode(payload));
                Token t = JSON.parseObject(jsonPayload, Token.class);
                if(t != null) {
                    MessageDigest digest = MessageDigest.getInstance("SHA-256");
                    digest.update(jsonPayload.getBytes());
                    digest.update(t.userId.getBytes());
                    digest.update(Token.secret.getBytes());
                    byte[] checkSign = digest.digest();
                    if(Arrays.equals(rawSign, checkSign))
                        return t;
                }
            }
            catch (Exception e) {
            }
            return null;
        }

        private static String cleanUUID() {
            UUID uuid = UUID.randomUUID();
            String raw = uuid.toString();
            return raw.replace("-", "");
        }
    }
}
